// Copyright (c) 2023 Presto Labs Pte. Ltd.
// Author: fengyang

#pragma once

#include "coin/feed/fastfeed/feed.pb.h"
#include "coin2/exchange/base/feed/update.h"
#include "coin2/exchange/base/feed/update_manager.h"
#include "coin2/feed/fastfeed/system.h"

namespace coin2::exchange::base::feed::proto {

using coin2::exchange::base::feed::FeedUpdate;
using ::fastfeed::proto::Feed;

namespace serialize {

class BookEventBuilder {
 public:
  BookEventBuilder(int depth)
  : depth_(depth) {
  }

  Feed ToProto(const FeedUpdate& upd) {
    const auto& book = upd.book();
    Feed proto;
    proto.Clear();
    proto.set_feed_type(fastfeed::proto::FeedType::FEED_TYPE_BOOK);
    proto.set_symbol(upd.product().absolute_norm());
    proto.set_timestamp(upd.timestamp());
    proto.set_origin_timestamp(book.Timestamp());

    // only publish snapshot first
    proto.set_data_type(fastfeed::proto::DataType::DATA_TYPE_BOOK_SNAPSHOT);
    auto pbook = proto.mutable_book_snapshot();
    int book_level = 0;
    for (auto const& [price, qty] : book.Asks()) {
      if (book_level >= this->depth_) break;

      auto level = pbook->add_ask();
      level->set_price_float(price);
      level->set_qty_float(qty);
      book_level++;
    }

    book_level = 0;
    for (auto const& [price, qty]: book.Bids()) {
      if (book_level >= this->depth_) break;

      auto level = pbook->add_bid();
      level->set_price_float(price);
      level->set_qty_float(qty);
      book_level++;
    }

    return proto;
  };

 private:
  int depth_;
};

class KlineEventBuilder {
 public:
  KlineEventBuilder() {
  }

  Feed ToProto(const FeedUpdate& upd) {
    const auto& klines = upd.klines();
    Feed proto;
    proto.Clear();
    proto.set_feed_type(fastfeed::proto::FeedType::FEED_TYPE_KLINE);
    proto.set_data_type(fastfeed::proto::DataType::DATA_TYPE_KLINE);
    const std::string& symbol = upd.product().absolute_norm();
    proto.set_symbol(symbol);
    proto.set_timestamp(upd.timestamp());
    proto.set_origin_timestamp(klines.Get().value()->timestamp);

    fastfeed::proto::KlineInterval interval = klines.Get().value()->interval;
    auto k = std::make_pair(symbol, interval);
    auto ts_iter = last_timestamp_.find(k);
    int64_t last_timestamp = 0;
    if (ts_iter != last_timestamp_.end()) {
      last_timestamp = ts_iter->second;
    }

    for (auto iter = klines.RBegin(); iter != klines.REnd(); iter++) {
      // only save new kline to fastfeed
      if (iter->open_timestamp < last_timestamp) continue;

      auto pkline = proto.mutable_kline_update()->add_kline();
      pkline->set_interval(iter->interval);
      pkline->set_open_timestamp(iter->open_timestamp);
      pkline->set_close_timestamp(iter->close_timestamp);
      pkline->set_open(iter->open);
      pkline->set_close(iter->close);
      pkline->set_high(iter->high);
      pkline->set_low(iter->low);
      if (!std::isnan(iter->volume)) pkline->set_volume(iter->volume);
      if (!std::isnan(iter->turnover)) pkline->set_turnover(iter->turnover);
      if (!std::isnan(iter->buy_volume)) pkline->set_buy_volume(iter->buy_volume);
      if (!std::isnan(iter->buy_turnover)) pkline->set_buy_turnover(iter->buy_turnover);
      last_timestamp_[k] = iter->open_timestamp;
    }
    return proto;
  };

 private:
  std::map<std::pair<std::string, fastfeed::proto::KlineInterval>, int64_t> last_timestamp_;
};

class TradeEventBuilder {
 public:
  static Feed ToProto(const FeedUpdate& upd) {
    const auto& trade = upd.trade();
    Feed proto;
    proto.Clear();
    proto.set_feed_type(fastfeed::proto::FeedType::FEED_TYPE_TRADE);
    proto.set_data_type(fastfeed::proto::DataType::DATA_TYPE_TRADE);
    proto.set_symbol(upd.product().absolute_norm());
    proto.set_timestamp(upd.timestamp());
    proto.set_origin_timestamp(trade.timestamp);

    auto ptrade = proto.mutable_trade();
    ptrade->set_price_float(trade.price);
    ptrade->set_qty_float(trade.fill_qty);

    if (trade.side == coin2::exchange::feed::TRADE_BUY_SIDE) {
      ptrade->set_side(fastfeed::proto::TRADE_SIDE_BUY);
    } else if (trade.side == coin2::exchange::feed::TRADE_SELL_SIDE) {
      ptrade->set_side(fastfeed::proto::TRADE_SIDE_SELL);
    } else {
      ptrade->set_side(fastfeed::proto::TRADE_SIDE_UNKNOWN);
    }

    return proto;
  }
};

class LiquidationEventBuilder {
 public:
  static Feed ToProto(const FeedUpdate& upd) {
    const auto& liquidation = upd.liquidation_order();
    Feed proto;
    proto.Clear();
    proto.set_feed_type(fastfeed::proto::FeedType::FEED_TYPE_LIQUIDATION_ORDER);
    proto.set_data_type(fastfeed::proto::DataType::DATA_TYPE_LIQUIDATION_ORDER);
    proto.set_symbol(upd.product().absolute_norm());
    proto.set_timestamp(upd.timestamp());
    proto.set_origin_timestamp(liquidation.timestamp);

    auto pliquidation = proto.mutable_liquidation();
    pliquidation->set_price_float(liquidation.price);
    pliquidation->set_qty_float(liquidation.qty);
    pliquidation->set_order_id(liquidation.order_id);

    if (liquidation.order_side == coin::proto::BUY_ORDER ||
        liquidation.order_side == coin::proto::BUY_CLOSE_ORDER ) {
      pliquidation->set_direction(fastfeed::proto::ORDER_DIRECTION_BUY);
    } else if (liquidation.order_side == coin::proto::SELL_ORDER ||
                liquidation.order_side == coin::proto::SELL_CLOSE_ORDER) {
      pliquidation->set_direction(fastfeed::proto::ORDER_DIRECTION_SELL);
    } else if (liquidation.order_side == coin::proto::UNKNOWN_ORDER_SIDE) {
      pliquidation->set_direction(fastfeed::proto::ORDER_DIRECTION_UNKNOWN);
    } else {
      THROW() << "NOT support liquidation order side " << liquidation.order_side;
    }

    return proto;
  }
};

class OpenInterestEventBuilder {
 public:
  static Feed ToProto(const FeedUpdate& upd) {
    const auto& open_interest = upd.open_interest();
    Feed proto;
    proto.Clear();
    proto.set_feed_type(fastfeed::proto::FeedType::FEED_TYPE_OPEN_INTEREST);
    proto.set_data_type(fastfeed::proto::DataType::DATA_TYPE_OPEN_INTEREST);
    proto.set_symbol(upd.product().absolute_norm());
    proto.set_timestamp(upd.timestamp());
    proto.set_origin_timestamp(open_interest.timestamp);

    auto popen_interest = proto.mutable_open_interest();
    popen_interest->set_qty_float(open_interest.open_interest_qty);

    return proto;
  }
};

class IndexEventBuilder {
 public:
  static Feed ToProto(const FeedUpdate& upd) {
    const auto& index = upd.index();
    Feed proto;
    proto.Clear();
    proto.set_feed_type(fastfeed::proto::FeedType::FEED_TYPE_INDEX);
    proto.set_data_type(fastfeed::proto::DataType::DATA_TYPE_INDEX);
    proto.set_symbol(upd.product().absolute_norm());
    proto.set_timestamp(upd.timestamp());
    proto.set_origin_timestamp(index.timestamp);

    auto pindex = proto.mutable_index();
    pindex->set_price_float(index.price);

    return proto;
  }
};

class MarkPriceEventBuilder {
 public:
  static Feed ToProto(const FeedUpdate& upd) {
    const auto& mark_price = upd.mark_price();
    Feed proto;
    proto.Clear();
    proto.set_feed_type(fastfeed::proto::FeedType::FEED_TYPE_MARK_PRICE);
    proto.set_data_type(fastfeed::proto::DataType::DATA_TYPE_MARK_PRICE);
    proto.set_symbol(upd.product().absolute_norm());
    proto.set_timestamp(upd.timestamp());
    proto.set_origin_timestamp(mark_price.timestamp);

    auto pmark_price = proto.mutable_mark_price();
    pmark_price->set_price_float(mark_price.mark_price);

    return proto;
  }
};

class FundingRateEventBuilder {
 public:
  static Feed ToProto(const FeedUpdate& upd) {
    const auto& funding_rate = upd.funding_rate();
    Feed proto;
    proto.Clear();
    proto.set_feed_type(fastfeed::proto::FeedType::FEED_TYPE_FUNDING_RATE);
    proto.set_data_type(fastfeed::proto::DataType::DATA_TYPE_FUNDING_RATE);
    proto.set_symbol(upd.product().absolute_norm());
    proto.set_timestamp(upd.timestamp());
    proto.set_origin_timestamp(funding_rate.timestamp);

    auto pfunding_rate = proto.mutable_funding_rate();
    pfunding_rate->set_funding_rate_float(funding_rate.funding_rate);
    pfunding_rate->set_funding_time(funding_rate.funding_time);
    pfunding_rate->set_estimated_rate_float(funding_rate.estimated_rate);

    return proto;
  }
};

class IvGreeksEventBuilder {
 public:
  static Feed ToProto(const FeedUpdate& upd) {
    const auto& iv_greeks = upd.iv_greeks();
    Feed proto;
    proto.Clear();
    proto.set_feed_type(fastfeed::proto::FeedType::FEED_TYPE_IV_GREEKS);
    proto.set_data_type(fastfeed::proto::DataType::DATA_TYPE_IV_GREEKS);
    proto.set_symbol(upd.product().absolute_norm());
    proto.set_timestamp(upd.timestamp());
    proto.set_origin_timestamp(iv_greeks.timestamp);

    auto piv_greeks = proto.mutable_iv_greeks();
    piv_greeks->set_ask_iv_float(iv_greeks.ask_iv);
    piv_greeks->set_bid_iv_float(iv_greeks.bid_iv);
    piv_greeks->set_mark_iv_float(iv_greeks.mark_iv);

    piv_greeks->set_delta_float(iv_greeks.delta);
    piv_greeks->set_gamma_float(iv_greeks.gamma);
    piv_greeks->set_rho_float(iv_greeks.rho);
    piv_greeks->set_theta_float(iv_greeks.theta);
    piv_greeks->set_vega_float(iv_greeks.vega);

    return proto;
  }
};

class NavEventBuilder {
 public:
  static Feed ToProto(const FeedUpdate& upd) {
    const auto& nav = upd.nav();
    Feed proto;
    proto.Clear();
    proto.set_feed_type(fastfeed::proto::FeedType::FEED_TYPE_NAV);
    proto.set_data_type(fastfeed::proto::DataType::DATA_TYPE_NAV);
    proto.set_symbol(upd.product().absolute_norm());
    proto.set_timestamp(upd.timestamp());
    proto.set_origin_timestamp(nav.timestamp);

    auto pnav = proto.mutable_nav();
    pnav->set_actual_leverage_float(nav.actual_leverage);
    pnav->set_nav_float(nav.nav);
    pnav->set_outstanding_float(nav.outstanding);
    for (const auto& ua : nav.basket) {
      auto basket = pnav->add_basket();
      basket->set_symbol_or_currency(ua.symbol_or_currency);
      basket->set_amount_float(ua.amount);
    }

    return proto;
  }
};

class WalletEventBuilder {
 public:
  static Feed ToProto(const FeedUpdate& upd) {
    const auto& wallet = upd.wallet();
    Feed proto;
    proto.Clear();
    proto.set_feed_type(fastfeed::proto::FeedType::FEED_TYPE_WALLET);
    proto.set_data_type(fastfeed::proto::DataType::DATA_TYPE_WALLET);
    proto.set_symbol(upd.product().absolute_norm());
    proto.set_timestamp(upd.timestamp());
    proto.set_origin_timestamp(wallet.timestamp);
    
    auto pwallet = proto.mutable_wallet();
    pwallet->set_can_deposit(wallet.can_deposit);
    pwallet->set_can_withdraw(wallet.can_withdraw);
    pwallet->set_blockchain_delay(wallet.blockchain_delay);

    return proto;
  }
};

class MarketAnalysisEventBuilder {
 public:
  static Feed ToProto(const FeedUpdate& upd) {
    const auto& market_analysis = upd.market_analysis();
    Feed proto;
    proto.Clear();
    proto.set_feed_type(fastfeed::proto::FeedType::FEED_TYPE_MARKET);
    proto.set_data_type(fastfeed::proto::DataType::DATA_TYPE_MARKET);
    proto.set_symbol(upd.product().absolute_norm());
    proto.set_timestamp(upd.timestamp());
    proto.set_origin_timestamp(market_analysis.timestamp);
    
    auto pmarket_analysis = proto.mutable_market_analysis();
    pmarket_analysis->set_total_supply(market_analysis.total_supply);
    pmarket_analysis->set_max_supply(market_analysis.max_supply);
    pmarket_analysis->set_circulating_supply(market_analysis.circulating_supply);
    pmarket_analysis->set_market_cap_in_usd(market_analysis.market_cap_in_usd);
    pmarket_analysis->set_tvl_in_usd(market_analysis.tvl_in_usd);
    pmarket_analysis->set_total_volume_in_usd(market_analysis.total_volume_in_usd);
    ::fastfeed::proto::VolumePerExchange* pvolume_per_exchange = nullptr;
    for (auto it = market_analysis.volume_per_exchange_list.begin(); it != market_analysis.volume_per_exchange_list.end(); it++) {
      pvolume_per_exchange = pmarket_analysis->add_volume_per_exchange_list();
      pvolume_per_exchange->set_exchange_name(it->exchange_name);
      pvolume_per_exchange->set_volume(it->volume);
      pvolume_per_exchange->set_volume_in_usd(it->volume_in_usd);
    }
    return proto;
  }
};

}  // namespace serialize

namespace deserialize {

class BookUpdateBuilder {
 public:
  static void FromProto(
      const Feed& proto,
      FeedUpdateManager* upd_mgr) {
    auto* book = upd_mgr->GetBookMutable(proto.native_symbol());
    CHECK(book);
    CHECK_EQ(proto.feed_type(), fastfeed::proto::FeedType::FEED_TYPE_BOOK);
    CHECK_EQ(proto.data_type(), fastfeed::proto::DataType::DATA_TYPE_BOOK_SNAPSHOT);
    const auto& snapshot = proto.book_snapshot();
    auto tx = book->NewTransaction(proto.origin_timestamp());
    tx.Clear();
    for (const auto& pq: snapshot.ask()) {
      tx.UpdateAsk(pq.price_float(), pq.qty_float());
    }
    for (const auto& pq: snapshot.bid()) {
      tx.UpdateBid(pq.price_float(), pq.qty_float());
    }
  }
};

class KlineUpdateBuilder {
 public:
  static void FromProto(
      const Feed& proto,
      FeedUpdateManager* upd_mgr) {
    // only support add for now
    CHECK_GT(proto.kline_update().kline_size(), 0);
    Klines* klines = nullptr;
    for (const auto& kline : proto.kline_update().kline()) {
      if (klines == nullptr) {
        klines = upd_mgr->GetKlineMutable(proto.native_symbol(), kline.interval());
        if (kline.interval() == fastfeed::proto::KLINE_INTERVAL_ROLLING_1DAY) {
          klines->SnapshotClear();
        }
      }

      // fastfeed only save freezed kline
      auto result = klines->AddKline(kline.open_timestamp());
      if (!result) continue;

      Kline* pKline;
      bool freezed;
      std::tie(pKline, freezed) = result.value();

      pKline->timestamp = proto.origin_timestamp();
      pKline->interval = kline.interval();
      pKline->open_timestamp = kline.open_timestamp();
      pKline->close_timestamp = kline.close_timestamp(),
      pKline->open = kline.open();
      pKline->close = kline.close();
      pKline->high = kline.high();
      pKline->low = kline.low();
      if (kline.volume() != 0) pKline->volume = kline.volume();
      if (kline.turnover() != 0) pKline->turnover = kline.turnover();
      if (kline.buy_volume() != 0) pKline->buy_volume = kline.buy_volume();
      if (kline.buy_turnover() != 0) pKline->buy_turnover = kline.buy_turnover();
    }
    klines->Freeze();
  }
};

class TradeUpdateBuilder {
 public:
  static void FromProto(
      const Feed& proto,
      FeedUpdateManager* upd_mgr) {
    CHECK_EQ(proto.feed_type(), fastfeed::proto::FeedType::FEED_TYPE_TRADE);
    CHECK_EQ(proto.data_type(), fastfeed::proto::DataType::DATA_TYPE_TRADE);
    const auto& trade = proto.trade();
    auto* _trade = upd_mgr->GetTradeMutable();
    _trade->timestamp = proto.origin_timestamp();
    _trade->price = trade.price_float();
    _trade->fill_qty = trade.qty_float();
    _trade->side = ::coin2::feed::fastfeed::impl::ConvertTradeSide(trade.side());
  }
};

class LiquidationUpdateBuilder {
 public:
  static void FromProto(
      const Feed& proto,
      FeedUpdateManager* upd_mgr) {
    CHECK_EQ(proto.feed_type(), fastfeed::proto::FeedType::FEED_TYPE_LIQUIDATION_ORDER);
    CHECK_EQ(proto.data_type(), fastfeed::proto::DataType::DATA_TYPE_LIQUIDATION_ORDER);
    const auto& liquidation = proto.liquidation();
    auto* _liquidation_order = upd_mgr->GetLiquidationOrderMutable();
    _liquidation_order->timestamp = proto.origin_timestamp();
    _liquidation_order->price = liquidation.price_float();
    _liquidation_order->qty = liquidation.qty_float();
    _liquidation_order->order_id = liquidation.order_id();

    coin::proto::OrderSide order_side
        = coin::proto::OrderSide::UNKNOWN_ORDER_SIDE;
    if (liquidation.direction() == ::fastfeed::proto::ORDER_DIRECTION_SELL) {
      order_side = coin::proto::OrderSide::SELL_ORDER;
    } else if (liquidation.direction() == ::fastfeed::proto::ORDER_DIRECTION_BUY) {
      order_side = coin::proto::OrderSide::BUY_ORDER;
    }
    _liquidation_order->order_side = order_side;
    _liquidation_order->order_type = coin::proto::OrderType::UNKNOWN_ORDER_TYPE;
    _liquidation_order->order_duration = coin::proto::OrderDuration::GTC_ORDER;
    _liquidation_order->order_state = coin::proto::OrderState::CREATED_ORDER;
  }
};

class OpenInterestUpdateBuilder {
 public:
  static void FromProto(
      const Feed& proto,
      FeedUpdateManager* upd_mgr) {
    CHECK_EQ(proto.feed_type(), fastfeed::proto::FeedType::FEED_TYPE_OPEN_INTEREST);
    CHECK_EQ(proto.data_type(), fastfeed::proto::DataType::DATA_TYPE_OPEN_INTEREST);
    const auto& open_interest = proto.open_interest();
    auto* _open_interest = upd_mgr->GetOpenInterestMutable();
    _open_interest->timestamp = proto.origin_timestamp();
    _open_interest->open_interest_qty = open_interest.qty_float();
  }
};

class IndexUpdateBuilder {
 public:
  static void FromProto(
      const Feed& proto,
      FeedUpdateManager* upd_mgr) {
    CHECK_EQ(proto.feed_type(), fastfeed::proto::FeedType::FEED_TYPE_INDEX);
    CHECK_EQ(proto.data_type(), fastfeed::proto::DataType::DATA_TYPE_INDEX);
    const auto& index = proto.index();
    auto* _index = upd_mgr->GetIndexMutable();
    _index->timestamp = proto.origin_timestamp();
    _index->price = index.price_float();
  }
};

class MarkPriceUpdateBuilder {
 public:
  static void FromProto(
      const Feed& proto,
      FeedUpdateManager* upd_mgr) {
    CHECK_EQ(proto.feed_type(), fastfeed::proto::FeedType::FEED_TYPE_MARK_PRICE);
    CHECK_EQ(proto.data_type(), fastfeed::proto::DataType::DATA_TYPE_MARK_PRICE);
    const auto& mark_price = proto.mark_price();
    auto* _mark_price = upd_mgr->GetMarkPriceMutable();
    _mark_price->timestamp = proto.origin_timestamp();
    _mark_price->mark_price = mark_price.price_float();
  }
};

class FundingRateUpdateBuilder {
 public:
  static void FromProto(
      const Feed& proto,
      FeedUpdateManager* upd_mgr) {
    CHECK_EQ(proto.feed_type(), fastfeed::proto::FeedType::FEED_TYPE_FUNDING_RATE);
    CHECK_EQ(proto.data_type(), fastfeed::proto::DataType::DATA_TYPE_FUNDING_RATE);
    const auto& funding_rate = proto.funding_rate();
    auto* _funding_rate = upd_mgr->GetFundingRateMutable();
    _funding_rate->timestamp = proto.origin_timestamp();
    _funding_rate->funding_rate = funding_rate.funding_rate_float();
    _funding_rate->funding_time = funding_rate.funding_time();
    _funding_rate->estimated_rate = funding_rate.estimated_rate_float();
  }
};

class IvGreeksUpdateBuilder {
 public:
  static void FromProto(
      const Feed& proto,
      FeedUpdateManager* upd_mgr) {
    CHECK_EQ(proto.feed_type(), fastfeed::proto::FeedType::FEED_TYPE_IV_GREEKS);
    CHECK_EQ(proto.data_type(), fastfeed::proto::DataType::DATA_TYPE_IV_GREEKS);
    const auto& iv_greeks = proto.iv_greeks();
    auto* _iv_greeks = upd_mgr->GetIvGreeksMutable();
    _iv_greeks->timestamp = proto.origin_timestamp();
    _iv_greeks->ask_iv = iv_greeks.ask_iv_float();
    _iv_greeks->bid_iv = iv_greeks.bid_iv_float();
    _iv_greeks->mark_iv = iv_greeks.mark_iv_float();
    _iv_greeks->delta = iv_greeks.delta_float();
    _iv_greeks->gamma = iv_greeks.gamma_float();
    _iv_greeks->rho = iv_greeks.rho_float();
    _iv_greeks->theta = iv_greeks.theta_float();
    _iv_greeks->vega = iv_greeks.vega_float();
  }
};

class NavUpdateBuilder {
 public:
  static void FromProto(
      const Feed& proto,
      FeedUpdateManager* upd_mgr) {
    CHECK_EQ(proto.feed_type(), fastfeed::proto::FeedType::FEED_TYPE_NAV);
    CHECK_EQ(proto.data_type(), fastfeed::proto::DataType::DATA_TYPE_NAV);
    const auto& nav = proto.nav();
    auto* _nav = upd_mgr->GetNavMutable();
    _nav->timestamp = proto.origin_timestamp();
    _nav->actual_leverage = nav.actual_leverage_float();
    _nav->nav = nav.nav_float();
    _nav->outstanding = nav.outstanding_float();
    _nav->nav = nav.nav_float();
    _nav->basket.clear();
    for (const auto& ua : nav.basket()) {
      _nav->basket.emplace_back(std::string(ua.symbol_or_currency()), ua.amount_float());
    }
  }
};

class WalletUpdateBuilder {
 public:
  static void FromProto(
      const Feed& proto,
      FeedUpdateManager* upd_mgr) {
    CHECK_EQ(proto.feed_type(), fastfeed::proto::FeedType::FEED_TYPE_WALLET);
    CHECK_EQ(proto.data_type(), fastfeed::proto::DataType::DATA_TYPE_WALLET);
    const auto& wallet = proto.wallet();
    auto* _wallet = upd_mgr->GetWalletMutable();
    _wallet->timestamp = proto.origin_timestamp();
    _wallet->can_deposit = wallet.can_deposit();
    _wallet->can_withdraw = wallet.can_withdraw();
    _wallet->blockchain_delay = wallet.blockchain_delay();
  }
};

class MarketAnalysisUpdateBuilder {
 public:
  static void FromProto(
      const Feed& proto,
      FeedUpdateManager* upd_mgr) {
    CHECK_EQ(proto.feed_type(), fastfeed::proto::FeedType::FEED_TYPE_MARKET);
    CHECK_EQ(proto.data_type(), fastfeed::proto::DataType::DATA_TYPE_MARKET);
    const auto& market_analysis = proto.market_analysis();
    auto* _market_analysis = upd_mgr->GetMarketAnalysisMutable();
    _market_analysis->timestamp = proto.origin_timestamp();
    _market_analysis->total_supply = market_analysis.total_supply();
    _market_analysis->max_supply = market_analysis.max_supply();
    _market_analysis->circulating_supply = market_analysis.circulating_supply();
    _market_analysis->market_cap_in_usd = market_analysis.market_cap_in_usd();
    _market_analysis->tvl_in_usd = market_analysis.tvl_in_usd();
    _market_analysis->total_volume_in_usd = market_analysis.total_volume_in_usd();
    _market_analysis->volume_per_exchange_list.clear();
    for (auto& volume_per_exchange : market_analysis.volume_per_exchange_list()) {
      _market_analysis->volume_per_exchange_list.emplace_back(
        volume_per_exchange.exchange_name(),
        volume_per_exchange.volume(),
        volume_per_exchange.volume_in_usd()
      );
    }
  }
};

}  // namespace deserialize

}  // namespace coin2::exchange::base::feed::proto